Põhjalik juhend TypeScript'i genereeriliste tüüpide kohta, mis käsitleb süntaksit, eeliseid, täpsemat kasutust ja parimaid praktikaid keerukate andmetüüpidega toimetulekuks globaalses tarkvaraarenduses.
TypeScript'i Genereerilised Tüübid: Keerukate Andmetüüpide Valdamine Töökindlate Rakenduste Jaoks
TypeScript, JavaScripti superkomplekt, annab arendajatele staatilise tüüpimisega võimaluse kirjutada töökindlamat ja hooldatavamat koodi. Selle üheks võimsamaks omaduseks on genereerilised tüübid, mis võimaldavad kirjutada koodi, mis suudab töötada erinevate andmetüüpidega, säilitades samal ajal tüübiohutuse. See juhend pakub põhjaliku ülevaate TypeScript'i genereerilistest tüüpidest, keskendudes nende rakendamisele keerukate andmetüüpide puhul globaalse tarkvaraarenduse kontekstis.
Mis on genereerilised tüübid?
Genereerilised tüübid pakuvad võimalust kirjutada taaskasutatavat koodi, mis saab töötada erinevate tüüpidega. Selle asemel, et kirjutada iga toetatava tüübi jaoks eraldi funktsioone või klasse, saate kirjutada ühe funktsiooni või klassi, mis kasutab tüübiparameetreid. Need tüübiparameetrid on kohatäitjad tegelikele tüüpidele, mida kasutatakse funktsiooni või klassi väljakutsumisel või instantseerimisel. See on eriti kasulik keerukate andmestruktuuride puhul, kus nende struktuuride sees olevate andmete tüüp võib varieeruda.
Genereeriliste tüüpide kasutamise eelised
- Koodi taaskasutatavus: Kirjutage kood üks kord ja kasutage seda erinevate tüüpidega. See vähendab koodi dubleerimist ja muudab teie koodibaasi hooldatavamaks.
- Tüübiohutus: Genereerilised tüübid võimaldavad TypeScripti kompilaatoril jõustada tüübiohutust kompileerimise ajal. See aitab vältida tüüpide mittevastavusest tulenevaid käitusaja vigu.
- Parem loetavus: Genereerilised tüübid muudavad teie koodi loetavamaks, näidates selgelt, milliste tüüpidega teie funktsioonid ja klassid on mõeldud töötama.
- Parem jõudlus: Mõnel juhul võivad genereerilised tüübid viia jõudluse paranemiseni, kuna kompilaator saab optimeerida genereeritud koodi kasutatavate konkreetsete tüüpide põhjal.
Genereeriliste tüüpide põhisüntaks
Genereeriliste tüüpide põhisüntaks hõlmab noolsulgude (< >) kasutamist tüübiparameetrite deklareerimiseks. Need tüübiparameetrid nimetatakse tavaliselt T, K, V jne, kuid võite kasutada mis tahes kehtivat identifikaatorit. Siin on lihtne näide genereerilisest funktsioonist:
function identity<T>(arg: T): T {
return arg;
}
let myString: string = identity<string>("hello");
let myNumber: number = identity<number>(123);
let myBoolean: boolean = identity<boolean>(true);
console.log(myString); // Väljund: hello
console.log(myNumber); // Väljund: 123
console.log(myBoolean); // Väljund: true
Selles näites deklareerib <T> tüübiparameetri nimega T. Funktsioon identity võtab argumendi tüübiga T ja tagastab väärtuse tüübiga T. Funktsiooni väljakutsumisel saate tüübiparameetri selgesõnaliselt määrata (nt identity<string>) või lasta TypeScriptil selle argumendi tüübi põhjal tuletada.
Töötamine keerukate andmetüüpidega
Genereerilised tüübid muutuvad eriti väärtuslikuks keerukate andmetüüpidega, nagu massiivid, objektid ja liidesed, tegelemisel. Uurime mõningaid levinud stsenaariume:
Genereerilised massiivid
Saate kasutada genereerilisi tüüpe, et luua funktsioone või klasse, mis töötavad erinevat tüüpi massiividega:
function arrayToString<T>(arr: T[]): string {
return arr.join(", ");
}
let numberArray: number[] = [1, 2, 3, 4, 5];
let stringArray: string[] = ["apple", "banana", "cherry"];
console.log(arrayToString(numberArray)); // Väljund: 1, 2, 3, 4, 5
console.log(arrayToString(stringArray)); // Väljund: apple, banana, cherry
Siin võtab funktsioon arrayToString massiivi tüübiga T[] ja tagastab massiivi string-esituse. See funktsioon töötab mis tahes tüüpi massiividega, muutes selle väga taaskasutatavaks.
Genereerilised objektid
Genereerilisi tüüpe saab kasutada ka funktsioonide või klasside määratlemiseks, mis töötavad erineva kujuga objektidega:
interface Person {
name: string;
age: number;
country: string; // Lisatud riik globaalse konteksti jaoks
}
interface Product {
id: number;
name: string;
price: number;
currency: string; // Lisatud valuuta globaalse konteksti jaoks
}
function displayInfo<T extends { name: string }>(item: T): void {
console.log(`Name: ${item.name}`);
}
let person: Person = { name: "Alice", age: 30, country: "USA" };
let product: Product = { id: 1, name: "Laptop", price: 1200, currency: "USD" };
displayInfo(person); // Väljund: Name: Alice
displayInfo(product); // Väljund: Name: Laptop
Selles näites võtab funktsioon displayInfo objekti tüübiga T, millel peab olema name omadus tüübiga string. Klausel extends { name: string } on kitsendus, mis määrab tüübiparameetri T miinimumnõuded. See tagab, et funktsioon pääseb ohutult ligi omadusele name.
Genereeriliste tüüpide täpsem kasutus
TypeScripti genereerilised tüübid pakuvad täpsemaid funktsioone, mis võimaldavad teil luua veelgi paindlikumat ja võimsamat koodi. Uurime mõnda neist funktsioonidest:
Mitu tüübiparameetrit
Saate määratleda funktsioone või klasse mitme tüübiparameetriga:
function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
interface Name {
firstName: string;
}
interface Age {
age: number;
}
const person: Name = { firstName: "Bob" };
const details: Age = { age: 42 };
const merged = merge(person, details);
console.log(merged.firstName); // Väljund: Bob
console.log(merged.age); // Väljund: 42
Funktsioon merge võtab kaks objekti tüüpidega T ja U ning tagastab uue objekti, mis sisaldab mõlema objekti omadusi. See on võimas viis andmete kombineerimiseks erinevatest allikatest.
Genereerilised kitsendused
Nagu varem näidatud, võimaldavad kitsendused piirata tüüpe, mida saab kasutada genereerilise tüübiparameetriga. See tagab, et genereeriline kood saab määratud tüüpidega ohutult töötada.
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity([1, 2, 3]); // Väljund: 3
loggingIdentity("hello"); // Väljund: 5
// loggingIdentity(123); // Viga: Tüübi 'number' argument ei ole määratav parameetrile tüübiga 'Lengthwise'.
Funktsioon loggingIdentity võtab argumendi tüübiga T, millel peab olema length omadus tüübiga number. See tagab, et funktsioon pääseb ohutult ligi omadusele length.
Genereerilised klassid
Genereerilisi tüüpe saab kasutada ka klassidega:
class DataStorage<T> {
private data: T[] = [];
addItem(item: T) {
this.data.push(item);
}
removeItem(item: T) {
this.data = this.data.filter(d => d !== item);
}
getItems(): T[] {
return [...this.data];
}
}
const textStorage = new DataStorage<string>();
textStorage.addItem("apple");
textStorage.addItem("banana");
textStorage.removeItem("apple");
console.log(textStorage.getItems()); // Väljund: [ 'banana' ]
const numberStorage = new DataStorage<number>();
numberStorage.addItem(1);
numberStorage.addItem(2);
numberStorage.removeItem(1);
console.log(numberStorage.getItems()); // Väljund: [ 2 ]
Klass DataStorage saab salvestada mis tahes tüüpi T andmeid. See võimaldab teil luua taaskasutatavaid andmestruktuure, mis on tüübiohutud.
Genereerilised liidesed
Genereerilised liidesed on kasulikud lepingute määratlemiseks, mis saavad töötada erinevate tüüpidega. Näiteks:
interface Result<T, E> {
success: boolean;
data?: T;
error?: E;
}
interface User {
id: number;
username: string;
email: string;
}
interface ErrorMessage {
code: number;
message: string;
}
function fetchUser(id: number): Result<User, ErrorMessage> {
if (id === 1) {
return { success: true, data: { id: 1, username: "john.doe", email: "john.doe@example.com" } };
} else {
return { success: false, error: { code: 404, message: "User not found" } };
}
}
const userResult = fetchUser(1);
if (userResult.success) {
console.log(userResult.data.username);
} else {
console.log(userResult.error.message);
}
Liides Result määratleb geneerilise struktuuri operatsiooni tulemuse esitamiseks. See võib sisaldada kas andmeid tüübiga T või viga tüübiga E. See on levinud muster asünkroonsete operatsioonide või ebaõnnestuda võivate operatsioonide käsitlemiseks.
Abitüübid ja genereerilised tüübid
TypeScript pakub mitmeid sisseehitatud abitüüpe, mis töötavad hästi genereeriliste tüüpidega. Need abitüübid aitavad teil tüüpe võimsal viisil teisendada ja manipuleerida.
Partial<T>
Partial<T> muudab kõik tüübi T omadused valikuliseks:
interface Person {
name: string;
age: number;
}
type PartialPerson = Partial<Person>;
const partialPerson: PartialPerson = { name: "Alice" }; // Kehtiv
Readonly<T>
Readonly<T> muudab kõik tüübi T omadused kirjutuskaitstuks:
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = Readonly<Person>;
const readonlyPerson: ReadonlyPerson = { name: "Bob", age: 42 };
// readonlyPerson.age = 43; // Viga: 'age' väärtust ei saa omistada, kuna see on kirjutuskaitstud omadus.
Pick<T, K>
Pick<T, K> valib tüübist T omaduste komplekti K:
interface Person {
name: string;
age: number;
email: string;
}
type NameAndAge = Pick<Person, "name" | "age">;
const nameAndAge: NameAndAge = { name: "Charlie", age: 28 };
Omit<T, K>
Omit<T, K> eemaldab tüübist T omaduste komplekti K:
interface Person {
name: string;
age: number;
email: string;
}
type PersonWithoutEmail = Omit<Person, "email">;
const personWithoutEmail: PersonWithoutEmail = { name: "David", age: 35 };
Record<K, T>
Record<K, T> loob tüübi, mille võtmed on K ja väärtused tüübiga T:
type CountryCodes = "US" | "CA" | "UK" | "DE" | "FR" | "JP" | "CN" | "IN" | "BR" | "AU"; // Laiendatud nimekiri globaalse konteksti jaoks
type Currency = "USD" | "CAD" | "GBP" | "EUR" | "JPY" | "CNY" | "INR" | "BRL" | "AUD"; // Laiendatud nimekiri globaalse konteksti jaoks
type CurrencyMap = Record<CountryCodes, Currency>;
const currencyMap: CurrencyMap = {
"US": "USD",
"CA": "CAD",
"UK": "GBP",
"DE": "EUR",
"FR": "EUR",
"JP": "JPY",
"CN": "CNY",
"IN": "INR",
"BR": "BRL",
"AU": "AUD",
};
Kaardistatud tüübid
Kaardistatud tüübid võimaldavad teil olemasolevaid tüüpe teisendada, itereerides üle nende omaduste. See on võimas viis uute tüüpide loomiseks olemasolevate põhjal. Näiteks saate luua tüübi, mis muudab kõik teise tüübi omadused kirjutuskaitstuks:
interface Person {
name: string;
age: number;
}
type ReadonlyPerson = {
readonly [K in keyof Person]: Person[K];
};
const readonlyPerson: ReadonlyPerson = { name: "Eve", age: 25 };
// readonlyPerson.age = 26; // Viga: 'age' väärtust ei saa omistada, kuna see on kirjutuskaitstud omadus.
Selles näites itereerib [K in keyof Person] üle kõigi Person liidese võtmete ja Person[K] pääseb ligi iga omaduse tüübile. Märksõna readonly muudab iga omaduse kirjutuskaitstuks.
Tingimuslikud tüübid
Tingimuslikud tüübid võimaldavad teil määratleda tüüpe tingimuste alusel. See on võimas viis luua tüüpe, mis kohanduvad erinevate stsenaariumidega.
type NonNullable<T> = T extends null | undefined ? never : T;
type MaybeString = string | null | undefined;
type StringType = NonNullable<MaybeString>; // string
function getValue<T>(value: T): NonNullable<T> {
if (value == null) { // Käsitleb nii nulli kui ka undefined'i
throw new Error("Value cannot be null or undefined");
}
return value as NonNullable<T>;
}
try {
const validValue = getValue("hello");
console.log(validValue.toUpperCase()); // Väljund: HELLO
const invalidValue = getValue(null); // See viskab vea
console.log(invalidValue); // Seda rida ei saavutata
} catch (error: any) {
console.error(error.message); // Väljund: Value cannot be null or undefined
}
Selles näites kontrollib tüüp NonNullable<T>, kas T on null või undefined. Kui on, tagastab see never, mis tähendab, et tüüp pole lubatud. Vastasel juhul tagastab see T. See võimaldab teil luua tüüpe, mis on garanteeritult mitte-nullitavad.
Parimad praktikad genereeriliste tüüpide kasutamiseks
Siin on mõned parimad praktikad, mida genereeriliste tüüpide kasutamisel meeles pidada:
- Kasutage kirjeldavaid tüübiparameetrite nimesid: Valige nimed, mis näitavad selgelt tüübiparameetri eesmärki.
- Kasutage kitsendusi, et piirata tüüpe, mida saab kasutada genereerilise tüübiparameetriga: See tagab, et teie genereeriline kood saab määratud tüüpidega ohutult töötada.
- Hoidke oma genereeriline kood lihtne ja keskendunud: Vältige oma genereerilise koodi liigset keerukust liiga paljude tüübiparameetrite või keeruliste kitsendustega.
- Dokumenteerige oma genereeriline kood põhjalikult: Selgitage tüübiparameetrite eesmärki ja kõiki kasutatavaid kitsendusi.
- Kaaluge kompromisse koodi taaskasutatavuse ja tüübiohutuse vahel: Kuigi genereerilised tüübid võivad parandada koodi taaskasutatavust, võivad need muuta teie koodi ka keerukamaks. Kaaluge enne genereeriliste tüüpide kasutamist eeliseid ja puudusi.
- Võtke arvesse lokaliseerimist ja globaliseerimist (l10n ja g11n): Kui tegelete andmetega, mida tuleb kuvada kasutajatele erinevates piirkondades, veenduge, et teie genereerilised tüübid toetaksid sobivaid vormindamis- ja kultuuritavasid. Näiteks võivad numbrite ja kuupäevade vormingud oluliselt erineda eri lokaatides.
Näited globaalses kontekstis
Vaatleme mõningaid näiteid, kuidas genereerilisi tüüpe saab kasutada globaalses kontekstis:
Valuutakonversioon
interface ConversionRate {
rate: number;
fromCurrency: string;
toCurrency: string;
}
function convertCurrency<T extends ConversionRate>(amount: number, rate: T): number {
return amount * rate.rate;
}
const usdToEurRate: ConversionRate = { rate: 0.85, fromCurrency: "USD", toCurrency: "EUR" };
const amountInUSD = 100;
const amountInEUR = convertCurrency(amountInUSD, usdToEurRate);
console.log(`${amountInUSD} USD is equal to ${amountInEUR} EUR`); // Väljund: 100 USD is equal to 85 EUR
Kuupäeva vormindamine
interface DateFormatOptions {
locale: string;
options: Intl.DateTimeFormatOptions;
}
function formatDate<T extends DateFormatOptions>(date: Date, format: T): string {
return date.toLocaleDateString(format.locale, format.options);
}
const currentDate = new Date();
const usDateFormat: DateFormatOptions = { locale: "en-US", options: { year: 'numeric', month: 'long', day: 'numeric' } };
const germanDateFormat: DateFormatOptions = { locale: "de-DE", options: { year: 'numeric', month: 'long', day: 'numeric' } };
const japaneseDateFormat: DateFormatOptions = { locale: "ja-JP", options: { year: 'numeric', month: 'long', day: 'numeric' } };
console.log("US Date: " + formatDate(currentDate, usDateFormat));
console.log("German Date: " + formatDate(currentDate, germanDateFormat));
console.log("Japanese Date: " + formatDate(currentDate, japaneseDateFormat));
Tõlketeenus
interface Translation {
[key: string]: string; // Võimaldab dünaamilisi keelevõtmeid
}
interface LanguageData<T extends Translation> {
languageCode: string;
translations: T;
}
const englishTranslations: Translation = {
"hello": "Hello",
"goodbye": "Goodbye",
"welcome": "Welcome to our website!"
};
const spanishTranslations: Translation = {
"hello": "Hola",
"goodbye": "Adiós",
"welcome": "¡Bienvenido a nuestro sitio web!"
};
const frenchTranslations: Translation = {
"hello": "Bonjour",
"goodbye": "Au revoir",
"welcome": "Bienvenue sur notre site web !"
};
const languageData: LanguageData<typeof englishTranslations>[] = [
{languageCode: "en", translations: englishTranslations },
{languageCode: "es", translations: spanishTranslations },
{languageCode: "fr", translations: frenchTranslations}
];
function translate<T extends Translation>(key: string, languageCode: string, languageData: LanguageData<T>[]): string {
const lang = languageData.find(lang => lang.languageCode === languageCode);
if (!lang) {
return `Translation for ${key} in ${languageCode} not found.`;
}
return lang.translations[key] || `Translation for ${key} not found.`;
}
console.log(translate("hello", "en", languageData)); // Väljund: Hello
console.log(translate("hello", "es", languageData)); // Väljund: Hola
console.log(translate("welcome", "fr", languageData)); // Väljund: Bienvenue sur notre site web !
console.log(translate("missingKey", "de", languageData)); // Väljund: Translation for missingKey in de not found.
Kokkuvõte
TypeScripti genereerilised tüübid on võimas tööriist taaskasutatava, tüübiohutu koodi kirjutamiseks, mis saab töötada keerukate andmetüüpidega. Mõistes genereeriliste tüüpide põhisüntaksit, täpsemaid funktsioone ja parimaid praktikaid, saate oluliselt parandada oma TypeScripti rakenduste kvaliteeti ja hooldatavust. Globaalsele publikule rakenduste arendamisel aitavad genereerilised tüübid teil hallata mitmekesiseid andmevorminguid ja kultuuritavasid, tagades sujuva kasutajakogemuse kõigile.